package com.example.gateway.filter;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: DELL
 * Date: 2025-03-10
 * Time: 20:58
 */

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.example.common.core.constants.CacheConstant;
import com.example.common.core.constants.HttpConstants;
import com.example.common.core.domain.LoginUser;
import com.example.common.core.domain.R;
import com.example.common.core.enums.ResultCode;
import com.example.common.core.enums.UserIdentity;
import com.example.common.core.utils.JwtUtils;
import com.example.common.redis.service.RedisService;
import com.example.gateway.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * ⽹关鉴权
 *
 */
@Slf4j
@Component
// GlobalFilter 全局过滤器, 拦截请求进行验证
// Ordered 进行拦截处理排序, 值越小越靠前先处理
public class AuthFilter implements GlobalFilter, Ordered {
    // 排除过滤的 uri ⽩名单地址，在nacos⾃⾏添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest(); // 获取请求
        String url = request.getURI().getPath(); // 获取请求的 url, 需要注意的是, 登录接口是不需要进行身份验证的

        // 跳过不需要验证的路径 ignoreWhite 为接口白名单
        if (matches(url, ignoreWhite.getWhites())) { // 判断当前接口是否在白名单中, 在则跳过验证
            // ignoreWhite.getWhites() 拿到 nacos 上的白名单
            return chain.filter(exchange);
        }

        // 执行到该处需要进行验证, 不在白名单中
        //从 http 请求头中获取 token, 通过 token 进行验证
        String token = getToken(request);
        if (StrUtil.isEmpty(token)) {
            // 不携带 token, 没有生成令牌
            // throw new RuntimeException("e"); 无法使用该方法捕获异常
            // 因为 SpringMVC 下设置的 throw, 而 SpringCloud 的 gateway 是基于 WebFlux 的, 因此要用 WebFlux 下的方法
            return unauthorizedResponse(exchange, "令牌不能为空");
        }

        Claims claims;
        try {
            // 防止 jwt 解析令牌出现问题, 需要捕获一下异常
            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析 payload 中信息, 存放着唯一标识信息
            if (claims == null) {
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
        }

        // 以下为通过 redis 中 key 是否存在来控制 jwt 的过期时间
        String userKey = JwtUtils.getUserKey(claims); //获取 jwt 中的key
        // getTokenKey(userKey) 去拼接成 loginToken + token 的形式
        boolean isLogin = redisService.hasKey(getTokenKey(userKey)); // 去 redis 中查询该拼接好的 userKey 是否存在
        if (!isLogin) {
            // 在 redis 中没查询到 userKey 表明已经过期, 但之前的令牌信息肯定是正确的, 因此登录状态过期
            return unauthorizedResponse(exchange, "登录状态已过期");
        }

        String userId = JwtUtils.getUserId(claims); //判断 jwt 中的信息是否完整
        // huTool 中的方法
        if (StrUtil.isEmpty(userId)) {
            //
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        // 此时 token 解析正确且未过期
        // 获取到 redis 中关于用户身份认证的敏感信息是否正确
        LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);
        // 根据 identity 来判断是普通用户还是管理员, 因此需要区分请求的路径是 C 端(只有普通用户操作)还是 B 端(管理员操作)
        // 通过路由地址来判断是 C 端还是 B端请求
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) &&
                !UserIdentity.ADMIN.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) &&
                !UserIdentity.ORDINARY.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        return chain.filter(exchange);
    }

    /**
     * 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串
     * @param url 指定url
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
        // 查找的 url 和链表地址为空则直接返回
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
        // 遍历链表, 如果查找的指定 url 在其中则直接返回
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                return true;
            }
        }

        // 查找地址不在链表中
        return false;
    }

    /**
     * 判断url是否与规则匹配
     * 匹配规则中：
     * ? 表⽰单个字符; 类似一个占位符
     * * 表⽰⼀层路径内的任意字符串，不可跨层级;
     * ** 表⽰任意层路径;
     *
     * @param pattern 匹配规则, 可以写一些特殊字符
     * @param url 需要匹配的url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstant.LOGIN_TOKEN + token;
    }
    /**
     * 从请求头中获取请求 token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }


    /**
     * 在未授权的情况下返回响应的错误信息和状态码并记录日志信息
     * @param exchange Spring WebFlux 的请求-响应交互对象
     * @param msg 需要返回给客户端的错误信息
     * @return Mono<Void> 因为在WebFlux中，响应处理是异步的，Mono<Void>表示处理完成后没有数据返回，但需要确保响应被正确写入
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    //拼装 WebFlux 模型响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        // 处理 HTTP 响应状态为 200
        response.setStatusCode(HttpStatus.OK);
        // 设置响应格式 Content-Type 为 application/json
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        // 创建一个带有错误码和错误信息的响应对象
        R<?> result = R.fail(code, msg);
        // 将响应对象转为 json 格式
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        // 将 json 数据写入相应主体并异步返回
        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return -200;  // 值越小越先被执行
    }

    public static void main(String[] args) {
        AuthFilter authFilter = new AuthFilter();

        // 测试 ? 表示单个字符
//        String pattern = "/sys/?bc";
//        System.out.println(authFilter.isMatch(pattern,"/sys/abc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/cbc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/acbc"));
//        System.out.println(authFilter.isMatch(pattern,"/sdsa/abc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/abcw"));

// 测试 * 表示任意字符, 但不能夸层级, eg: "/sys/a/b/bc"
//        String pattern = "/sys/*/bc";
//        System.out.println(authFilter.isMatch(pattern,"/sys/a/bc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/sdasdsadsad/bc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/a/b/bc"));
//        System.out.println(authFilter.isMatch(pattern,"/a/b/bc"));
//        System.out.println(authFilter.isMatch(pattern,"/sys/a/b/"));

// 测试 ** 表示任意层路径
        String pattern = "/sys/**/bc";
        System.out.println(authFilter.isMatch(pattern, "/sys/a/bc")); // true
        System.out.println(authFilter.isMatch(pattern,"/sys/sdasdsadsad/bc")); // true
        System.out.println(authFilter.isMatch(pattern, "/sys/a/b/bc")); // true
        System.out.println(authFilter.isMatch(pattern,"/sys/a/b/s/23/432/fdsf///bc")); // true
        System.out.println(authFilter.isMatch(pattern,"/a/b/s/23/432/fdsf///bc")); // false
        System.out.println(authFilter.isMatch(pattern,"/sys/a/b/s/23/432/fdsf///")); // false

    }
}
